// Mobx 状态仓库
import { observable, action, configure, runInAction, computed, autorun, when, reaction } from 'mobx';

// 开启严格模式，所有改变状态的函数必须生命为 action 否则报错
configure({
    enforceActions: 'observed'
});

export class CounterStore {
    constructor() {
        // autorun(() = {}) 当入参函数内被观察对象发生变化时，会重新执行一遍该函数
        autorun(
            () => {
                console.log('autorun invoking!');
                if (this.count < 0 || this.price < 0) {
                    alert("count & price wouldn't be negative");
                    runInAction(() => {
                        this.count = 0;
                        this.price = 0;
                    });
                    throw new Error("You typed in a negative number! Don't do that!");
                }
            },
            {
                delay: 1000,
                onError: e => {
                    console.log('Error catched!');
                }
            }
        );

        /*
        when() 函数接收两个参数:
            - 第一个参数接收一个返回布尔类型值得函数。`when` 会监听该函数内部的可观察对象的变化，当可观察对象发生变化时会自动执行第一个函数。
            - 第二个参数接收一个执行函数。当`when` 监听的第一个参数函数返回值时 `true` 时，立即执行该执行函数。

        when() 的第二个参数函数执行完毕后，该自动运行程序（`when` 本身）会被销毁。也就是说，`when()` 第一个函数返回一次 `true` 之后，便去执行第二个执行函数，然后销毁了自己。
         */
        when(
            () => this.count >= 10,
            () => {
                if(this.interval){
                    clearInterval(this.interval)
                }
                alert('The maximum count is 15. You\'re getting close to that number')
            }
        );

        reaction(
            () => {
                console.log('reaction invoking!')
                return this.count
            },
            (count) => {
                if(count > 15){
                    if(!window.confirm('Your input have over the maximum limit! Please confirm your behavior is what you determined to.')){
                        this.count = 15
                    }
                }
                
            }
        )
    }

    // @observanle 将属性标记成可被观测的属性，当该属性发生变化时，会通知所有使用该属性的地方进行更新
    @observable count = 1;
    @observable price = 0;
    // 任何改变 observables 状态的函数，都应该被标记为 action。且只有在 action 中才能修改状态
    @action
    addCount = () => {
        this.count++;
    };

    // @action.bound：用于给函数绑定 this。
    // 因为经常将 action 传递给其他组件使用，不绑定 this 会导致 this 动态指向传入的组件
    // 箭头函数不需要使用 action.bound。因为箭头函数中的 this 在定义的时候就已经时绑定过的了，不需要重复绑定。
    // @action.bound addCount(){
    //     this.count++
    // }

    /* 
    异步 action 实现方法一： 将回调函数声明成动作，并在异步操作中直接调用
    @action 
    autoAdd = () => {
        setInterval(() => {
            this.addCallback()
        }, 1000)
    }

    @action 
    addCallback = () => {
        this.count = this.count + 3
    } 
    */

    /* 
    异步 action 实现方法二：使用 `action()` 包裹回调函数，使之声明为一个动作
    @action 
    autoAdd = () => {
        setInterval(() => {
            action('addCallback',() => {this.count = this.count + 3})()
        }, 1000)
    } 
    */

    /*
    异步 action 实现方法三：
     使用 `runInAction` 工具函数包裹函数中修改状态的部分，而不是将整个回调函数都声明成一个动作，这种方式时鼓励用户不要到处写 action，而是在在整个函数过程结束时竟可能多的对所有状态进行修改。
    */

    interval = null

    @action
    autoAdd = () => {
        this.interval = setInterval(() => {
            runInAction(() => {
                this.count = this.count + 3;
            });
        }, 1000);
    };

    /*
    async 函数中：
    `await` 之后状态修改的代码都不会被包装成动作。
    使用 `runInAction` 即可将 `await` 异步后面的状态修改包装成动作 
     */
    @action delayAdd = async () => {
        await this.delayAddCallback();
        runInAction(() => {
            this.count = this.count + 10;
        });
    };

    delayAddCallback = () => {
        setTimeout(() => {
            alert('Delay success！');
        }, 1000);
    };

    @action
    onChangePrice = e => {
        this.price = e.target.value;
    };

    @action
    onChangePriceTotal = e => {
        this.total = e.target.value;
    };

    /* 
    当 `computed` 中任意被观测的状态或者计算值发生变化时，该计算值会重新计算并返回新的值。 
    */
    @computed
    get total() {
        return this.price * this.count;
    }

    set total(total) {
        this.price = total / this.count;
    }
}

export const createCounterStore = () => new CounterStore();
